package com.hk.core.autoconfigure.web.reactive;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.hk.core.autoconfigure.web.ApplicationInfo;
import com.hk.core.autoconfigure.web.ApplicationInfoCustomizer;
import com.hk.core.autoconfigure.web.WebContextConfiguration;
import com.hk.core.web.reactive.Webs;
import com.hk.core.web.reactive.filters.RepeatBodyFilter;
import com.hk.core.web.reactive.filters.XssFilter;
import com.hk.core.web.reactive.method.annotations.QueryModelRequestBodyMethodArgumentResolver;
import io.netty.channel.ChannelOption;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;
import lombok.SneakyThrows;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.annotation.Order;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.client.ReactorResourceFactory;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.web.filter.reactive.ServerWebExchangeContextFilter;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer;
import org.springframework.web.reactive.socket.server.WebSocketService;
import org.springframework.web.server.WebFilter;
import reactor.netty.resources.ConnectionProvider;
import reactor.netty.resources.LoopResources;

import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Objects;


/**
 * Reactive web configurer
 *
 * @author Kevin
 * @date 2021-07-17 16:26
 */
@AutoConfiguration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
public class ReactiveCustomConfigurer implements WebFluxConfigurer {

    private final ApplicationInfo applicationInfo;

    private final ApplicationContext applicationContext;

    public ReactiveCustomConfigurer(ObjectProvider<List<ApplicationInfoCustomizer>> applicationInfoCustomizers, ApplicationContext applicationContext) {
        ApplicationInfo applicationInfo = new ApplicationInfo();
        var applicationInfoCustomizerList = applicationInfoCustomizers.getIfAvailable();
        if (Objects.nonNull(applicationInfoCustomizerList)) {
//            OrderComparator.sort(applicationInfoCustomizerList); //不需要再排序了，Spring已默认排好序
            applicationInfoCustomizerList.forEach(item -> item.customizer(applicationInfo));
        }
        this.applicationInfo = applicationInfo;
//        ApplicationInfo.ReactiveInfo reactive = this.applicationInfo.getReactiveInfo();
//        if(Objects.isNull(reactive)){
//            this.applicationInfo.setReactiveInfo(this.applicationInfo.new ReactiveInfo());//创建内部类对象
//        }
        this.applicationContext = applicationContext;
    }

    /**
     * 配置这个Filter后， 才能在 {@link Webs#getServerWebExchange()} 获取到当前请求的 ServerWebExchange
     */
    @Order(value = 0)
    @Bean(name = "serverWebExchangeContextFilter")
    public WebFilter serverWebExchangeContextFilter() {
        return new ServerWebExchangeContextFilter();
    }

    @Bean
    @Order(-1)
    public WebFilter xssFilter() {
        return new XssFilter(applicationInfo.getReactiveInfo().getXssRequestPredicate(), applicationInfo.getXssStringEscapeHandler());
    }

    @Bean
    public RepeatBodyFilter repeatBodyFilter() {
        return new RepeatBodyFilter();
    }

    @Override
    public void addFormatters(FormatterRegistry registry) {
        WebContextConfiguration.addFormatters(registry);
    }

    /**
     * reactive 配置websocket service
     */
    @Override
    public WebSocketService getWebSocketService() {
        return WebFluxConfigurer.super.getWebSocketService();
    }


    // 如果有排序需求，不能在这个方法中添加自定义的MethodArgumentResolver，因为在Spring中写死了，
    // 源码参考: org.springframework.web.reactive.result.method.annotation.ControllerMethodResolver.initResolvers
    @Override
    public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
        ReactiveAdapterRegistry registry = applicationContext.getBean(ReactiveAdapterRegistry.class);
        configurer.addCustomResolver(new QueryModelRequestBodyMethodArgumentResolver(ServerCodecConfigurer.create().getReaders(), registry));
    }

    /**
     * 配置 webClient
     *
     * @param builder      {@link org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration}
     * @param objectMapper {@link JacksonAutoConfiguration.JacksonObjectMapperConfiguration#jacksonObjectMapper)}
     */
    @Bean
    @SneakyThrows
    @ConditionalOnMissingBean(value = {WebClient.class})
    public WebClient defaultWebClient(WebClient.Builder builder, ObjectMapper objectMapper) {
        ReactorResourceFactory reactorResourceFactory = new ReactorResourceFactory();
        reactorResourceFactory.setUseGlobalResources(false);
        reactorResourceFactory.setLoopResources(LoopResources.create("tcp-connect-loop", 32, true));
        // 设置一个loop进行http线程管理
        reactorResourceFactory.setConnectionProvider(ConnectionProvider.builder("tcp-connect-loop").pendingAcquireTimeout(Duration.ofSeconds(10)) // 等待超时时间
                .maxConnections(64) // 最大连接数
                .pendingAcquireMaxCount(300) // 等待队列大小
                .maxIdleTime(Duration.ofSeconds(200)).maxLifeTime(Duration.ofSeconds(200)).build());
        // 配置https 支持
        var sslContext = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build();
        return builder.codecs(configurer -> {
                    // 配置Jackson 编码解码器
                    configurer.customCodecs().register(new Jackson2JsonEncoder(objectMapper));
                    configurer.customCodecs().register(new Jackson2JsonDecoder(objectMapper));
                    // 编解码器对在内存中缓冲数据大小,不配置如果内容过大时会报错: Exceeded limit on max bytes to buffer : 262144
                    configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024);
                })
                //配置默认请求头
//                .filter((request, next) -> { // 过滤器
////                    var filtered = ClientRequest.from(request).header("", "'value").build() //如果要新添加一些数据
//                    log.info("requestUrl:{},requestMethod:{}", request.url(), request.method().name());
//                    return next.exchange(request);
//                })
                .clientConnector(new ReactorClientHttpConnector(reactorResourceFactory, (client) -> {
                    client.compress(true).secure(sslContextSpec -> sslContextSpec.sslContext(sslContext)).compress(true).option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5_000) //设置连接超时
                            // 设置连接后的读、写超时
                            .doOnConnected(connection -> connection.addHandlerLast(new ReadTimeoutHandler(10)).addHandlerFirst(new WriteTimeoutHandler(10))).responseTimeout(Duration.of(10, ChronoUnit.SECONDS)); //设置响应超时时间
                    return client;
                })).build();
    }
}
